package com.syntax.processor;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

import javapoet.syntax.com.annotation.SyntaxGen;

public class SyntaxProcessor extends AbstractProcessor {

    private Trees mTrees;
    private TreeMaker mTreeMaker;
    private Name.Table mNames;
    private Context mContext;
    private JavacElements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        mTrees = Trees.instance(processingEnv);
        mContext = ((JavacProcessingEnvironment)processingEnv).getContext();
        mTreeMaker = TreeMaker.instance(mContext);
        mNames = Names.instance(mContext).table;
        mElementUtils = (JavacElements) processingEnv.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(SyntaxGen.class.getCanonicalName() );
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        if (roundEnvironment.processingOver()) {
            return true;
        }

        processGen(roundEnvironment);

        return false;
    }

    private void processGen(RoundEnvironment roundEnv) {
        Set<? extends Element> elements =
                roundEnv.getElementsAnnotatedWith(SyntaxGen.class);

        String enclosingClass = null;
        List<FieldDesc> views = new ArrayList<>();

        for (Element each : elements) {

            Element enclosingElement = each.getEnclosingElement();
            JCTree tree = (JCTree) mTrees.getTree(enclosingElement);
            //tree.accept(new DeprecatedTranslator(roundEnv, each, mTreeMaker, mMethodHelper, mElementUtils));

            if (!(each instanceof Symbol.VarSymbol)) {
                continue;
            }

            Symbol.VarSymbol symbol = (Symbol.VarSymbol) each;
            String viewClass = symbol.asType().toString();
            System.out.println("each : " + viewClass);
            System.out.println("name : " + each.getSimpleName());
            System.out.println("tree enclosing : " + enclosingElement);

            int resId = each.getAnnotation(SyntaxGen.class).value();
            if (enclosingClass == null) {
                enclosingClass = enclosingElement.toString();
                System.out.println("class : " + enclosingClass);
            }

            views.add(new FieldDesc(viewClass, each.getSimpleName().toString(), resId));
        }

        if (enclosingClass != null) {
            generateClass(enclosingClass, views);
        }
    }

    private void generateClass(String originClass, List<FieldDesc> views) {

        String pkg = originClass.substring(0, originClass.lastIndexOf("."));
        String originClassName = originClass.substring(originClass.lastIndexOf(".") + 1);
        String newClass = originClassName + "_Bind";

        MethodSpec main = MethodSpec.methodBuilder("main")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(String[].class, "args")
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                .build();

        ClassName activityClass = ClassName.get(pkg, originClassName);
        FieldSpec activity = FieldSpec.builder(activityClass, "mA", Modifier.PRIVATE)
                .addModifiers(Modifier.PRIVATE, Modifier.PRIVATE)
                .build();

        List<FieldSpec> specs = new ArrayList<>();
        List<IdSt> sts = new ArrayList<>();
        for (FieldDesc desc : views) {
            String val = desc.viewClass;
            String pre = getPre(val);
            String suffix = getSuffix(val);

            ClassName viewClass = ClassName.get(pre, suffix);
            FieldSpec viewDef = FieldSpec.builder(viewClass, "mView_" + desc.id, Modifier.PUBLIC)
                    //.addModifiers(Modifier.PRIVATE, Modifier.PRIVATE)
                    .build();
            specs.add(viewDef);

            String st0 = "$N = $N.$N = this.$N.findViewById($L)";
            String stPre = "mView_" + desc.id;
            String st1 = "mA";
            String st2 = desc.fieldName;
            String st3 = "mA";
            int st4 = desc.id;
            sts.add(new IdSt(st0, stPre, st1, st2, st3, st4));
        }

        MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(activityClass, "activity")
                .addStatement("this.$N = $N", "mA", "activity");

        for (IdSt st : sts) {
            constructor.addStatement(st.st0, st.stPre, st.st1, st.st2, st.st3, st.st4);
        }

        TypeSpec.Builder builder = TypeSpec.classBuilder(newClass)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(constructor.build())
                .addMethod(main)
                .addField(activity);

        for (FieldSpec spec : specs) {
            builder.addField(spec);
        }

        JavaFile javaFile = JavaFile.builder(pkg, builder.build())
                .build();

        try {
            javaFile.writeTo(System.out);
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getPre(String className) {
        return className.substring(0, className.lastIndexOf("."));
    }

    public String getSuffix(String className) {
        return className.substring(className.lastIndexOf(".") + 1);
    }

    class IdSt {
        public IdSt(String st0, String stPre, String st1, String st2, String st3, int st4) {
            this.st0 = st0;
            this.stPre = stPre;
            this.st1 = st1;
            this.st2 = st2;
            this.st3 = st3;
            this.st4 = st4;
        }

        String st0;
        String stPre;
        String st1;
        String st2;
        String st3;
        int st4;
    }

    class FieldDesc {

        public FieldDesc(String claxx, String name, int id) {
            viewClass = claxx;
            fieldName = name;
            this.id = id;
        }

        int id;
        String viewClass;
        String fieldName;
    }
}
